package com.flex.showdoc4j.executor;

import com.flex.showdoc4j.config.Configuration;
import com.flex.core.enums.ParamType;
import com.flex.showdoc4j.executor.interfaces.ParameterResolver;
import com.flex.showdoc4j.model.DocModel;
import com.flex.showdoc4j.model.ParamModel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.Data;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author ganlt<br>
 * @since 2019/07/12 16:02 <br>
 */
public class ParameterResolverMapping implements ParameterResolver {

    @Override
    public List<Type> signalParameters(Wrapper wrapper) {
        List<Type> clazzSignal = new ArrayList<>();
        Configuration configuration = wrapper.getConfiguration();
        Object controlBean = configuration.getControlBean(wrapper.getPatternUrl());
        HandlerMethod handlerMethod = (HandlerMethod) controlBean;
        MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
        for (MethodParameter parameter : methodParameters) {
            Type parameterType = parameter.getGenericParameterType();
            if (parameterType instanceof ParameterizedType) {
                clazzSignal.add(parameterType);
            } else {
                clazzSignal.add(parameterType);
            }
        }
        return clazzSignal;
    }

    @Override
    public List<?> resolver(Wrapper wrapper) {
        Configuration configuration = wrapper.getConfiguration();
        List<Object> realityObjList = new ArrayList<>();
        Object controlBean = configuration.getControlBean(wrapper.getPatternUrl());
        if (controlBean instanceof HandlerMethod) {
            List<ParamModel> paramModels = wrapper.paramModelList();
            DocModel docModel = wrapper.getDocModel();

            List<Type> clazzSignal = signalParameters(wrapper);
            //填充数据值
            Object requestMap = paramObjectToMap(paramModels, docModel);
            String requestJson = JSON.toJSONString(requestMap);
            for (Type type : clazzSignal) {
                Class<?> paramClass = (Class<?>) getType(type);
                Object realityObj = null;
                if (List.class.isAssignableFrom(paramClass)) {
                    if (requestMap instanceof List) {
                        @SuppressWarnings("unchecked")
                        List<ParamInnerType> paramInnerTypes = (List<ParamInnerType>) requestMap;
                        if (paramInnerTypes.size() > 0) {
                            ParamInnerType paramInnerType = paramInnerTypes.get(0);
                            requestJson = JSON.toJSONString(Collections.singletonList(paramInnerType.getValue()), SerializerFeature.WriteEnumUsingToString);
                            realityObj = JSON.parseArray(requestJson, paramInnerType.getClazz());
                        }
                    }
                } else {
                    if (requestMap instanceof Map) {
                        findInnerParamMap((Map<String, Object>) requestMap);
                        requestJson = JSON.toJSONString(requestMap);
                        realityObj = JSON.parseObject(requestJson, type);
                    } else {
                        realityObj = JSON.parseObject(requestJson, type);
                    }
                }

                realityObjList.add(realityObj);
            }
        }
        return realityObjList;
    }

    private void findInnerParamMap(Map<String, Object> requestMap) {
        Map<String, Object> map = requestMap;
        for (Map.Entry<String, Object> entryMap : map.entrySet()) {
            Object value = entryMap.getValue();
            if (value instanceof List) {
                List<ParamInnerType> paramInnerType = (List<ParamInnerType>) value;
                String key = entryMap.getKey();
                map.put(key, paramInnerType.get(0).getValue());
            } else if (value instanceof Map){
                findInnerParamMap((Map<String, Object>) value);
            }
        }
    }

    private Type getType(Type type) {
        Type resultArgType = type;
        Type resultType = type;
        if (resultType instanceof ParameterizedType) {
            resultArgType = ParameterizedType.class.cast(resultType).getRawType();
        }

        return  resultArgType;
    }

    private Object paramObjectToMap(List<ParamModel> paramModels, DocModel docModel) {
        Map<String, Object> paramMap = new HashMap<>(10);
        if (docModel.isPage()) {
            Map<String, Object> contentMap = new HashMap<>(10);
            for (ParamModel paramModel : paramModels) {
                if (paramModel.getType() == ParamType.LIST) {
                    List<ParamInnerType> arrList = new ArrayList<>();
                    Object arrObj = null;
                    try {
                        arrObj = JSON.parseArray(paramModel.getVal(), paramModel.getClazz());
                    } catch (Exception e) {
                        arrObj = JSON.parseObject(paramModel.getVal(), paramModel.getClazz());
                    }
                    ParamInnerType paramInnerType = new ParamInnerType();
                    paramInnerType.setClazz(paramModel.getClazz());
                    paramInnerType.setValue(arrObj);
                    arrList.add(paramInnerType);
                    if (paramModels.size() > 1) {
                        contentMap.put(paramModel.getName(), arrList);
                    } else {

                    }
                } else {
                    contentMap.put(paramModel.getName(), paramModel.getVal());
                }
            }
            paramMap.put("content", contentMap);

            Map<String, Object> pager = new HashMap<>(3);
            pager.put("current", docModel.getPager()[0]);
            pager.put("size", docModel.getPager()[1]);
            paramMap.put("pager", pager);
            return paramMap;
        } else {
            for (ParamModel paramModel : paramModels) {
                if (paramModel.getType() == ParamType.LIST) {
                    List<ParamInnerType> arrList = new ArrayList<>();
                    Object arrObj = null;
                    try {
                        arrObj = JSON.parseArray(paramModel.getVal(), paramModel.getClazz());
                    } catch (Exception e) {
                        arrObj = JSON.parseObject(paramModel.getVal(), paramModel.getClazz());
                    }
                    ParamInnerType paramInnerType = new ParamInnerType();
                    paramInnerType.setClazz(paramModel.getClazz());
                    paramInnerType.setValue(arrObj);
                    arrList.add(paramInnerType);
                    if (paramModels.size() > 1) {
                        paramMap.put(paramModel.getName(), arrList);
                    } else {
                        return arrList;
                    }
                } else {
                    paramMap.put(paramModel.getName(), paramModel.getVal());
                }
            }
            return paramMap;
        }

    }

    @Data
    private static class ParamInnerType {
        /**
         * 参数类
         */
        private Class<?> clazz;
        /**
         * 参数值
         */
        private Object value;
    }
}
